/**
    Copyright (C) 2012 Guido Ludwig

    This file is part of JATT.
	http://code.google.com/p/jatt/

    JATT is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package de.guidoludwig.jatt.util;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

import javax.imageio.ImageIO;

/**
 * Image Compression Utility
 * 
 * @author Guido Ludwig
 * 
 */
public class JATTCompressor {

	private final int preferredWidth;
	private final long preferredSize;
	private final boolean convertToJPEG;
	private final File tempDirectory;

	public JATTCompressor(int preferredWidth, long preferredSize)
			throws IOException {
		this.preferredWidth = preferredWidth;
		this.preferredSize = preferredSize;
		this.convertToJPEG = true;
		this.tempDirectory = JATTUtil.createTempDirectory();
	}

	public ImageInfo optimize(ImageInfo best) throws IOException {
		if (!ImageInfo.MIME_TYPE_JPG.equals(best.getMimeType())
				&& !ImageInfo.MIME_TYPE_PNG.equals(best.getMimeType())) {
			best = convertToJPEG(best);
		}
		if (!sizeFits(best)) {
			best = resizeImage(best);
		}
		return toMaxSize(best);
	}

	private File compress(ImageInfo original, String outputname,
			float compressionQuality) throws IOException {
		if (!original.supportsCompression()) {
			throw new IllegalArgumentException("Mime type "
					+ original.getMimeType() + "does not support compression");
		}
		String mimeType = original.getMimeType();
		BufferedImage image = original.getBufferedImage();
		File outputFile = new File(outputname);
		ImageUtil.writeFile(image, mimeType, outputFile, compressionQuality);
		return outputFile;
	}

	private ImageInfo convertToJPEG(ImageInfo original) throws IOException {
		BufferedImage originalBI = original.getBufferedImage();

		if( originalBI.getColorModel().getTransparency() != Transparency.OPAQUE) {
			originalBI = fillTransparentPixels(originalBI, Color.WHITE);
		}
		// int type = originalBI.getType() == 0? BufferedImage.TYPE_INT_ARGB :
		// originalBI.getType();
		int type = originalBI.getType() == 0 ? BufferedImage.TYPE_INT_RGB
				: originalBI.getType();
		BufferedImage newBufferedImage = new BufferedImage(
				originalBI.getWidth(), originalBI.getHeight(), type);

		// write data into an RGB buffered image, no transparency
		Graphics2D g = getConfiguredGraphics(newBufferedImage);
		g.drawImage(originalBI, 0, 0, Color.WHITE, null);

		// write to jpeg file
		File jpgFile = new File(tempDirectory, original.getBaseName() + ".jpg");
		ImageIO.write(newBufferedImage, "jpg", jpgFile);
		return new ImageInfo(jpgFile);
	}

	public static BufferedImage fillTransparentPixels(BufferedImage image,
			Color fillColor) {
		int w = image.getWidth();
		int h = image.getHeight();
		BufferedImage image2 = new BufferedImage(w, h,
				BufferedImage.TYPE_INT_RGB);
		Graphics2D g = image2.createGraphics();
		g.setColor(fillColor);
		g.fillRect(0, 0, w, h);
		g.drawRenderedImage(image, null);
		g.dispose();
		return image2;
	}

	private Graphics2D getConfiguredGraphics(BufferedImage image) {
		Graphics2D g = image.createGraphics();
		g.setComposite(AlphaComposite.Src);
		g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
				RenderingHints.VALUE_INTERPOLATION_BICUBIC);
		g.setRenderingHint(RenderingHints.KEY_RENDERING,
				RenderingHints.VALUE_RENDER_QUALITY);
		g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);
		return g;
	}

	private boolean sizeFits(ImageInfo original) {
		return original.getSize() < preferredSize;
	}

	private ImageInfo toMaxSize(ImageInfo original) throws IOException {

		if (sizeFits(original)) {
			return original;
		}
		if (!original.supportsCompression()) {
			if (!convertToJPEG) {
				return original;
			}
			original = convertToJPEG(original);
			if (sizeFits(original)) {
				return original;
			}
		}
		float originalSize = original.getSize();
		File temp = null;

		// Strip exif data first !
		float upperSize = originalSize;
		float previousSize = originalSize;
		float minCompression = 0.2f;
		float upperCompression = 1;
		float lowerCompression = minCompression;

		float minBytes = preferredSize * 0.98f;
		long newSize = 0;
		int tries = 0;
		int maxTries = 10;
		float nextCompression = preferredSize / upperSize;
		ImageInfo current = null;
		while ((newSize < minBytes || newSize > preferredSize)
				&& tries < maxTries) {
			tries++;
			temp = File.createTempFile("" + nextCompression, ".jpg");
			// temp = new File(original.getBaseName() + nextCompression + "." +
			// original.getExtension());
			File newFile = compress(original, temp.getAbsolutePath(),
					nextCompression);
			current = new ImageInfo(newFile);
			newSize = current.getSize();
			if (newSize > preferredSize) {
				upperCompression = nextCompression;
				upperSize = newSize;
			} else {
				lowerCompression = nextCompression;
			}
			nextCompression = (lowerCompression + upperCompression) * 0.5f;
			if (previousSize * 1.01 > newSize && previousSize * .99 < newSize) {
				break;
			}
			previousSize = newSize;
		}
		if (tries > 0) {
			Path in = FileSystems.getDefault().getPath(
					current.getFile().getAbsolutePath());

			String compressedFileName = original.getBaseName() + "COMPRESSED."
					+ current.getExtension();
			File output = new File(tempDirectory, compressedFileName);
			Path out = FileSystems.getDefault().getPath(
					output.getAbsolutePath());
			Files.copy(in, out, StandardCopyOption.REPLACE_EXISTING);
			current = new ImageInfo(output);
		}
		return current;

	}

	private ImageInfo resizeImage(ImageInfo original) throws IOException {

		BufferedImage bi = original.getBufferedImage();
		Image scaledImage = null;
		if (bi.getWidth() <= preferredWidth) {
			return original;
		}
		float oldheight = bi.getHeight();
		float oldwidth = bi.getWidth();
		float newHeight = oldheight * (preferredWidth / oldwidth);
		int height = Float.valueOf(newHeight).intValue();

		scaledImage = bi.getScaledInstance(preferredWidth, -1,
				Image.SCALE_SMOOTH);

		BufferedImage resizedImage = new BufferedImage(preferredWidth, height,
				BufferedImage.TYPE_INT_RGB);
		Graphics g = getConfiguredGraphics(resizedImage);
		g.drawImage(scaledImage, 0, 0, preferredWidth, height, null);
		g.dispose();

		String compressedFileName = original.getBaseName() + "RESIZED."
				+ original.getExtension();
		File outputFile = new File(tempDirectory, compressedFileName);
		ImageUtil.writeFile(resizedImage, original.getMimeType(), outputFile,
				1.0F);
		ImageInfo r = new ImageInfo(outputFile);
		return r;
	}

}
